package org.musmlib;

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Iterator;
import java.util.Map;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import com.google.common.collect.ImmutableList;
import com.google.common.collect.Maps;

import static com.google.common.base.Preconditions.*;

/**
 * MasterStatus Servlet.<p>
 * 
 * Demonstration of <u>Master-Slave Pattern</u>:<p>
 * 
 * Three identical Servlet instances queried. Each is at a different network 
 * 	location. Equivalent results required from two out of the three 
 * 	instances.<p>
 * 
 * MasterStatus has same interface as Status. MasterStatus receives same input 
 * 	as Status slaves. Input is posted to three slaves and responses analyzed. 
 * 	The results which two or more respond with are what MasterSlave in turn 
 * 	responds.<p>
 * 
 * @see Status
 */

public class MasterStatus extends HttpServlet
{
	private final static long serialVersionUID = 400006484505193640L;

	private final Map<String,StringBuilder> _slaves = 
			Maps.newHashMap();

	private final Map<String,Integer> _slaveResultsTally = 
			Maps.newHashMap();
	
	private ImmutableList<String> _parameters = ImmutableList.of(
			"website-status-url", 
			"content-expected" );
	
	
	@Override
	public void init()
	{
		_slaves
		.put( "http://127.0.0.1:8080/Status/Slave", 
			  new StringBuilder() );
		
		_slaves
		.put( "http://musmlib.org:8080/Status/", 
			  new StringBuilder() );
		
		_slaves
		.put( "http://1-dot-sse661status.appspot.com/Status", 
			  new StringBuilder() );
		
	}//method
	
	
	@Override
	public void doPost( final HttpServletRequest req, 
		  			    final HttpServletResponse resp ) throws IOException 
    {				
		final String[] slaveSites = _slaves.keySet().toArray(new String[]{});

		resp.setContentType( "text/plain" );
		
		for( Integer cx = 0; cx < slaveSites.length; cx++ )
		{
			/* clear any previous results in StringBuilders, although should 
			 * be empty, redefined for each POST  
			 */
			
			_slaves.get(slaveSites[cx]).setLength(0);
			
			try
			{
			
			/* gather incoming POST input, re-POST to slaves */

			String slaveURL = slaveSites[cx];
			
			//resp.getOutputStream().println( "processing slave: " + slaveURL );
			//resp.flushBuffer();

			checkNotNull( slaveURL, "slave website URL null" );
			
			checkArgument( slaveURL.length() > 0, "slave website URL empty" );
			
			String formData = "";
			
			Iterator<String> parms = _parameters.iterator();
			
			while( parms.hasNext() )
			{
				String parmName = parms.next();

				String parmValue = req.getParameter( parmName );
				
				formData += parmName + "=" + parmValue + "&";

				//resp.getOutputStream().println( "formData: " + formData );
				//resp.flushBuffer();
				
			}//while
			
			formData = formData.replaceFirst("\\&$", "");
				
			URL url = new URL( slaveURL ); 
			HttpURLConnection conn = (HttpURLConnection) url.openConnection();           
			conn.setDoOutput(true);
			conn.setDoInput(true);
			conn.setConnectTimeout(20000);
			conn.setInstanceFollowRedirects(false); 
			conn.setRequestMethod("POST"); 
			conn.setRequestProperty("Content-Type", 
									"application/x-www-form-urlencoded"); 
			conn.setRequestProperty("Charset", "utf-8");
			conn.setRequestProperty("Content-Length", 
					Integer.toString(formData.getBytes().length));
			conn.setUseCaches (false);

			DataOutputStream outStream = new DataOutputStream(conn
					.getOutputStream());
			outStream.writeBytes(formData);
			outStream.flush();
			outStream.close();
			
			BufferedReader in = new BufferedReader(
					new InputStreamReader( conn.getInputStream() ));

			StringBuilder results = _slaves.get(slaveURL);

			String line;
			while(( line = in.readLine()) != null )
				results.append( line + "\n" );
			in.close();
			
			conn.disconnect();
			
			}
			catch( final IOException err )
			{
				_slaves.get(slaveSites[cx]).append( err.toString() );
				
			}//try
			
		}//while


		/* determine which identical result obtained from 2+ slaves */

		final Iterator<String> slaves = _slaves.keySet().iterator();
		
		while( slaves.hasNext() )
		{
			String currentSlave = slaves.next();
			
			String result = _slaves.get(currentSlave).toString();
			
			resp.getOutputStream().println( "slave: " + currentSlave 
					+ " results : \n" 
					+ result );

			if( ! _slaveResultsTally.containsKey(result) )
			{
				_slaveResultsTally.put(result, 1);
				
			}
			else
			{
				 Integer count = _slaveResultsTally.get(result) + 1;
				
				 _slaveResultsTally.put( result, count );
				 
			}//if
			
		}//while

		
		final Iterator<String> resultsTallies = _slaveResultsTally.keySet()
				.iterator();

		Boolean agreementReached = false;
		
		while( resultsTallies.hasNext() )
		{
			String result = resultsTallies.next();
			
			if( _slaveResultsTally.get(result) > 1 )
			{
				agreementReached = true;
				
				resp.getOutputStream().println( "\nResults from 2+ slaves:\n" 
						+ result );
				
				break;
				
			}//if
			
		}//while
		
		if( ! agreementReached )
		{
			resp.getOutputStream().println( "\nResults were varied. Failed to "
					+ "obtain same results from 2+ slaves");
			
		}//if
		
    }//method
	
	
}//class